luci-app-adblock: sync with release 4.4.0-1
authorDirk Brenken <[email protected]>
Thu, 10 Apr 2025 21:43:46 +0000 (23:43 +0200)
committerDirk Brenken <[email protected]>
Thu, 10 Apr 2025 21:44:04 +0000 (23:44 +0200)
* major LuCI frontend improvements, incl. Custom Feed Editor
* synchronized with the banIP front-end level

Signed-off-by: Dirk Brenken <[email protected]>
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/allowlist.js [new file with mode: 0644]
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blacklist.js [deleted file]
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blocklist.js [new file with mode: 0644]
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/custom.css [new file with mode: 0644]
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/dnsreport.js
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/feeds.js [new file with mode: 0644]
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/overview.js
applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/whitelist.js [deleted file]
applications/luci-app-adblock/root/usr/share/luci/menu.d/luci-app-adblock.json
applications/luci-app-adblock/root/usr/share/rpcd/acl.d/luci-app-adblock.json

diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/allowlist.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/allowlist.js
new file mode 100644 (file)
index 0000000..e6374e3
--- /dev/null
@@ -0,0 +1,58 @@
+'use strict';
+'require view';
+'require fs';
+'require ui';
+
+let localFile = '/etc/adblock/adblock.allowlist';
+let notMsg, errMsg;
+
+return view.extend({
+       load: function () {
+               return L.resolveDefault(fs.stat(localFile), "")
+                       .then(function (stat) {
+                       if (!stat) {
+                               return fs.write(localFile, "");
+                       }
+                       return Promise.all([
+                               L.resolveDefault(fs.stat(localFile), ""),
+                               L.resolveDefault(fs.read_direct(localFile), "")
+                       ]);
+               });
+       },
+       render: function (allowlist) {
+               if (allowlist[0] && allowlist[0].size >= 100000) {
+                       document.body.scrollTop = document.documentElement.scrollTop = 0;
+                       ui.addNotification(null, E('p', _('The allowlist is too big, unable to save modifications.')), 'error');
+               }
+               return E('div', { 'class': 'cbi-section cbi-section-descr' }, [
+                       E('p', _('This is the local adblock allowlist to always-allow certain domains.<br /> \
+                               <em><b>Please note:</b></em> add only one domain per line. Comments introduced with \'#\' are allowed - ip addresses, wildcards and regex are not.')),
+                       E('textarea', {
+                               'style': 'width: 100% !important; padding: 5px; font-family: monospace; margin-top: .4em',
+                               'spellcheck': 'false',
+                               'wrap': 'off',
+                               'rows': 25
+                       }, [allowlist[1] != null ? allowlist[1] : ''])
+               ]);
+       },
+       handleSave: function (ev) {
+               let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n/g, '\n')) + '\n';
+               return fs.write(localFile, value)
+                       .then(function () {
+                               document.querySelector('textarea').value = value;
+                               document.body.scrollTop = document.documentElement.scrollTop = 0;
+                               if (!notMsg) {
+                                       ui.addNotification(null, E('p', _('Allowlist modifications have been saved, reload adblock that changes take effect.')), 'info');
+                                       notMsg = true;
+                               }
+                       }).catch(function (e) {
+                               document.body.scrollTop = document.documentElement.scrollTop = 0;
+                               if (!errMsg) {
+                                       ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error');
+                                       errMsg = true;
+                               }
+                       });
+       },
+       handleSaveApply: null,
+       handleReset: null
+});
diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blacklist.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blacklist.js
deleted file mode 100644 (file)
index 9f5c6c4..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-'use strict';
-'require view';
-'require fs';
-'require ui';
-
-return view.extend({
-       load: function() {
-               return L.resolveDefault(fs.read_direct('/etc/adblock/adblock.blacklist'), '');
-       },
-       handleSave: function(ev) {
-               var value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n/g, '\n')) + '\n';
-               return fs.write('/etc/adblock/adblock.blacklist', value)
-                       .then(function(rc) {
-                               document.querySelector('textarea').value = value;
-                               ui.addNotification(null, E('p', _('The changes to the blacklist have been saved. Reload your adblock lists for the changes to take effect.')), 'info');
-                       }).catch(function(e) {
-                               ui.addNotification(null, E('p', _('Unable to save changes: %s').format(e.message)));
-                       });
-       },
-       render: function(blacklist) {
-               return E([
-                       E('p', {},
-                               _('This is the local adblock blacklist to always-deny certain (sub) domains.<br /> \
-                               Please note: add only one domain per line. Comments introduced with \'#\' are allowed - ip addresses, wildcards and regex are not.')),
-                       E('p', {},
-                               E('textarea', {
-                                       'style': 'width: 100% !important; padding: 5px; font-family: monospace',
-                                       'spellcheck': 'false',
-                                       'wrap': 'off',
-                                       'rows': 25
-                               }, [ blacklist != null ? blacklist : '' ])
-                       )
-               ]);
-       },
-       handleSaveApply: null,
-       handleReset: null
-});
diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blocklist.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/blocklist.js
new file mode 100644 (file)
index 0000000..9fec089
--- /dev/null
@@ -0,0 +1,58 @@
+'use strict';
+'require view';
+'require fs';
+'require ui';
+
+let localFile = '/etc/adblock/adblock.blocklist';
+let notMsg, errMsg;
+
+return view.extend({
+       load: function () {
+               return L.resolveDefault(fs.stat(localFile), "")
+                       .then(function (stat) {
+                       if (!stat) {
+                               return fs.write(localFile, "");
+                       }
+                       return Promise.all([
+                               L.resolveDefault(fs.stat(localFile), ""),
+                               L.resolveDefault(fs.read_direct(localFile), "")
+                       ]);
+               });
+       },
+       render: function (blocklist) {
+               if (blocklist[0] && blocklist[0].size >= 100000) {
+                       document.body.scrollTop = document.documentElement.scrollTop = 0;
+                       ui.addNotification(null, E('p', _('The blocklist is too big, unable to save modifications.')), 'error');
+               }
+               return E('div', { 'class': 'cbi-section cbi-section-descr' }, [
+                       E('p', _('This is the local adblock blocklist to always-block certain domains.<br /> \
+                               <em><b>Please note:</b></em> add only one domain per line. Comments introduced with \'#\' are allowed - ip addresses, wildcards and regex are not.')),
+                       E('textarea', {
+                               'style': 'width: 100% !important; padding: 5px; font-family: monospace; margin-top: .4em',
+                               'spellcheck': 'false',
+                               'wrap': 'off',
+                               'rows': 25
+                       }, [blocklist[1] != null ? blocklist[1] : ''])
+               ]);
+       },
+       handleSave: function (ev) {
+               let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n/g, '\n')) + '\n';
+               return fs.write(localFile, value)
+                       .then(function () {
+                               document.querySelector('textarea').value = value;
+                               document.body.scrollTop = document.documentElement.scrollTop = 0;
+                               if (!notMsg) {
+                                       ui.addNotification(null, E('p', _('Blocklist modifications have been saved, reload adblock that changes take effect.')), 'info');
+                                       notMsg = true;
+                               }
+                       }).catch(function (e) {
+                               document.body.scrollTop = document.documentElement.scrollTop = 0;
+                               if (!errMsg) {
+                                       ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error');
+                                       errMsg = true;
+                               }
+                       });
+       },
+       handleSaveApply: null,
+       handleReset: null
+});
diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/custom.css b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/custom.css
new file mode 100644 (file)
index 0000000..0209f7d
--- /dev/null
@@ -0,0 +1,9 @@
+.cbi-input-text {
+       width: 90% !important;
+       margin-bottom: -5px;
+       padding-top: 0rem;
+}
+.cbi-input-select {
+       margin-bottom: -5px;
+       padding-top: 0rem;
+}
index 010e728cf11dd3b2b57e4626f15735cd5dd95c87..e8806db3e9ee8f55a06811887a31081e81d02532 100644 (file)
@@ -7,12 +7,12 @@
        button handling
 */
 function handleAction(ev) {
-       if (ev.target && ev.target.getAttribute('name') === 'blacklist') {
-               L.ui.showModal(_('Add Blacklist Domain'), [
-                       E('p', _('Add this (sub-)domain to your local blacklist.')),
+       if (ev.target && ev.target.getAttribute('name') === 'blocklist') {
+               L.ui.showModal(_('Add Blocklist Domain'), [
+                       E('p', _('Add this (sub-)domain to your local blocklist.')),
                        E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
                                E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
-                                       E('input', { 'class': 'cbi-input-text', 'style': 'width:300px', 'spellcheck': 'false', 'id': 'blacklist', 'value': ev.target.getAttribute('value') }, [])
+                                       E('input', { 'class': 'cbi-input-text', 'style': 'width:300px', 'spellcheck': 'false', 'id': 'blocklist', 'value': ev.target.getAttribute('value') }, [])
                                ])
                        ]),
                        E('div', { 'class': 'right' }, [
@@ -24,14 +24,14 @@ function handleAction(ev) {
                                E('button', {
                                        'class': 'btn cbi-button-action',
                                        'click': ui.createHandlerFn(this, function(ev) {
-                                               L.resolveDefault(fs.read_direct('/etc/adblock/adblock.blacklist'), '')
+                                               L.resolveDefault(fs.read_direct('/etc/adblock/adblock.blocklist'), '')
                                                .then(function(res) {
-                                                       var domain = document.getElementById('blacklist').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g,'');
+                                                       var domain = document.getElementById('blocklist').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g,'');
                                                        var pattern = new RegExp('^' + domain.replace(/[\.]/g,'\\.') + '$', 'm');
                                                        if (res.search(pattern) === -1) {
-                                                               var blacklist = res + domain + '\n';
-                                                               fs.write('/etc/adblock/adblock.blacklist', blacklist);
-                                                               ui.addNotification(null, E('p', _('Blacklist changes have been saved. Refresh your adblock lists that changes take effect.')), 'info');
+                                                               var blocklist = res + domain + '\n';
+                                                               fs.write('/etc/adblock/adblock.blocklist', blocklist);
+                                                               ui.addNotification(null, E('p', _('Blocklist modifications have been saved, reload adblock that changes take effect.')), 'info');
                                                        }
                                                        L.hideModal();
                                                });
@@ -39,15 +39,15 @@ function handleAction(ev) {
                                }, _('Save'))
                        ])
                ]);
-               document.getElementById('blacklist').focus();
+               document.getElementById('blocklist').focus();
        }
 
-       if (ev.target && ev.target.getAttribute('name') === 'whitelist') {
-               L.ui.showModal(_('Add Whitelist Domain'), [
-                       E('p', _('Add this (sub-)domain to your local whitelist.')),
+       if (ev.target && ev.target.getAttribute('name') === 'allowlist') {
+               L.ui.showModal(_('Add Allowlist Domain'), [
+                       E('p', _('Add this (sub-)domain to your local allowlist.')),
                        E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
                                E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
-                                       E('input', { 'class': 'cbi-input-text', 'style': 'width:300px', 'spellcheck': 'false', 'id': 'whitelist', 'value': ev.target.getAttribute('value') }, [])
+                                       E('input', { 'class': 'cbi-input-text', 'style': 'width:300px', 'spellcheck': 'false', 'id': 'allowlist', 'value': ev.target.getAttribute('value') }, [])
                                ])
                        ]),
                        E('div', { 'class': 'right' }, [
@@ -59,14 +59,14 @@ function handleAction(ev) {
                                E('button', {
                                        'class': 'btn cbi-button-action',
                                        'click': ui.createHandlerFn(this, function(ev) {
-                                               L.resolveDefault(fs.read_direct('/etc/adblock/adblock.whitelist'), '')
+                                               L.resolveDefault(fs.read_direct('/etc/adblock/adblock.allowlist'), '')
                                                .then(function(res) {
-                                                       var domain = document.getElementById('whitelist').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g,'');
+                                                       var domain = document.getElementById('allowlist').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g,'');
                                                        var pattern = new RegExp('^' + domain.replace(/[\.]/g,'\\.') + '$', 'm');
                                                        if (res.search(pattern) === -1) {
-                                                               var whitelist = res + domain + '\n';
-                                                               fs.write('/etc/adblock/adblock.whitelist', whitelist);
-                                                               ui.addNotification(null, E('p', _('Whitelist changes have been saved. Refresh your adblock lists that changes take effect.')), 'info');
+                                                               var allowlist = res + domain + '\n';
+                                                               fs.write('/etc/adblock/adblock.allowlist', allowlist);
+                                                               ui.addNotification(null, E('p', _('Allowlist modifications have been saved, reload adblock that changes take effect.')), 'info');
                                                        }
                                                        L.hideModal();
                                                });
@@ -74,7 +74,7 @@ function handleAction(ev) {
                                }, _('Save'))
                        ])
                ]);
-               document.getElementById('whitelist').focus();
+               document.getElementById('allowlist').focus();
        }
 
        if (ev === 'query') {
@@ -282,18 +282,18 @@ return view.extend({
                                        button = E('button', {
                                                'class': 'btn cbi-button cbi-button-positive',
                                                'style': 'word-break: inherit',
-                                               'name': 'whitelist',
+                                               'name': 'allowlist',
                                                'value': content.requests[i].domain,
                                                'click': handleAction
-                                       }, [ _('Whitelist...') ]);
+                                       }, [ _('Allowlist...') ]);
                                } else {
                                        button = E('button', {
                                                'class': 'btn cbi-button cbi-button-negative',
                                                'style': 'word-break: inherit',
-                                               'name': 'blacklist',
+                                               'name': 'blocklist',
                                                'value': content.requests[i].domain,
                                                'click': handleAction
-                                       }, [ _('Blacklist...') ]);
+                                       }, [ _('Blocklist...') ]);
                                }
                                rows_requests.push([
                                        content.requests[i].date,
@@ -326,22 +326,7 @@ return view.extend({
                                E('div', { 'class': 'cbi-value' }, [
                                        E('div', { 'class': 'cbi-value-title', 'style': 'float:left;width:230px' }, _('Blocked DNS Requests')),
                                        E('div', { 'class': 'cbi-value-title', 'id': 'blocked', 'style': 'float:left;color:#37c' }, (content.blocked || '-') + ' (' + (content.percent || '-') + ')')
-                               ]),
-                               E('div', { 'class': 'right' }, [
-                                       E('button', {
-                                               'class': 'btn cbi-button cbi-button-apply',
-                                               'click': ui.createHandlerFn(this, function() {
-                                                       return handleAction('query');
-                                               })
-                                       }, [ _('Blocklist Query...') ]),
-                                       '\xa0\xa0\xa0',
-                                       E('button', {
-                                               'class': 'btn cbi-button cbi-button-positive',
-                                               'click': ui.createHandlerFn(this, function() {
-                                                       return handleAction('refresh');
-                                               })
-                                       }, [ _('Refresh...') ])
-                               ]),
+                               ])
                        ]),
                        E('div', { 'class': 'cbi-section' }, [
                                E('div', { 'class': 'left' }, [
@@ -355,7 +340,24 @@ return view.extend({
                                        E('h3', _('Latest DNS Requests')),
                                        tbl_requests
                                ])
-                       ])
+                       ]),
+                       E('div', { 'class': 'cbi-page-actions' }, [
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-apply',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'click': ui.createHandlerFn(this, function() {
+                                               return handleAction('query');
+                                       })
+                               }, [ _('Blocklist Query...') ]),
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-positive important',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'click': ui.createHandlerFn(this, function() {
+                                               return handleAction('refresh');
+                                       })
+                               }, [ _('Refresh...') ])
+                       ]),
+
                ]);
        },
        handleSaveApply: null,
diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/feeds.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/feeds.js
new file mode 100644 (file)
index 0000000..acfa899
--- /dev/null
@@ -0,0 +1,292 @@
+'use strict';
+'require view';
+'require form';
+'require fs';
+'require ui';
+
+/*
+       include custom CSS
+*/
+document.querySelector('head').appendChild(E('link', {
+       'rel': 'stylesheet',
+       'type': 'text/css',
+       'href': L.resource('view/adblock/custom.css')
+}));
+
+/*
+       observe DOM changes
+*/
+const observer = new MutationObserver(function (mutations) {
+       if (mutations) {
+               const inputs = document.querySelectorAll('input');
+               inputs.forEach(function (input) {
+                       input.setAttribute('autocomplete', 'off')
+                       input.setAttribute('autocorrect', 'off')
+                       input.setAttribute('autocapitalize', 'off')
+                       input.setAttribute('spellcheck', false)
+               })
+               const labels = document.querySelectorAll('label[for^="widget.cbid.json"][for$="name"]');
+               labels.forEach(function (label) {
+                       label.setAttribute("style", "font-weight: bold !important; color: #595 !important;");
+               })
+               L.resolveDefault(fs.stat('/etc/adblock/adblock.custom.feeds'), '').then(function (stat) {
+                       const buttons = document.querySelectorAll('#btnClear, #btnCreate, #btnSave, #btnUpload, #btnDownload');
+                       if (buttons[1] && buttons[2] && stat.size === 0) {
+                               buttons[1].removeAttribute('disabled');
+                               buttons[2].removeAttribute('disabled');
+                       } else if (buttons[0] && buttons[3] && buttons[4] && stat.size > 0) {
+                               buttons[0].removeAttribute('disabled');
+                               buttons[3].removeAttribute('disabled');
+                               buttons[4].removeAttribute('disabled');
+                       }
+               });
+       }
+});
+
+const targetNode = document.getElementById('view');
+const observerConfig = {
+       childList: true,
+       subtree: true,
+       attributes: false,
+       characterData: false
+};
+observer.observe(targetNode, observerConfig);
+
+/*
+       button handling
+*/
+function handleEdit(ev) {
+       if (ev === 'upload') {
+               return ui.uploadFile('/etc/adblock/adblock.custom.feeds').then(function () {
+                       L.resolveDefault(fs.read_direct('/etc/adblock/adblock.custom.feeds', 'json'), "").then(function (data) {
+                               if (data) {
+                                       let dataLength = Object.keys(data).length || 0;
+                                       if (dataLength > 0) {
+                                               for (let i = 0; i < dataLength; i++) {
+                                                       let feed = Object.keys(data)[i];
+                                                       let descr = data[feed].descr;
+                                                       if (feed && descr) {
+                                                               continue;
+                                                       }
+                                                       fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
+                                                               return ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
+                                                       });
+                                               }
+                                       } else {
+                                               fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
+                                                       return ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
+                                               });
+                                       }
+                                       location.reload();
+                               } else {
+                                       fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
+                                               return ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
+                                       });
+                               }
+                       });
+               }).catch(function () { });
+       }
+       if (ev === 'download') {
+               return fs.read_direct('/etc/adblock/adblock.custom.feeds', 'blob').then(function (blob) {
+                       let url = window.URL.createObjectURL(blob),
+                               date = new Date(),
+                               name = 'adblock.custom.feeds_%04d-%02d-%02d.json'.format(date.getFullYear(), date.getMonth() + 1, date.getDate()),
+                               link = E('a', { 'style': 'display:none', 'href': url, 'download': name });
+                       document.body.appendChild(link);
+                       link.click();
+                       document.body.removeChild(link);
+                       window.URL.revokeObjectURL(url);
+               }).catch(function () { });
+       }
+       if (ev === 'create') {
+               return fs.read_direct('/etc/adblock/adblock.feeds', 'json').then(function (content) {
+                       fs.write('/etc/adblock/adblock.custom.feeds', JSON.stringify(content)).then(function () {
+                               location.reload();
+                       });
+               });
+       }
+       if (ev === 'clear') {
+               return fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
+                       location.reload();
+               });
+       }
+       if (ev === 'save') {
+               const invalid = document.querySelectorAll('.cbi-input-invalid');
+               if (invalid.length > 0) {
+                       document.body.scrollTop = document.documentElement.scrollTop = 0;
+                       return ui.addNotification(null, E('p', _('Invalid input values, unable to save modifications.')), 'error');
+               }
+       }
+       let sumSubElements = [], exportJson;
+       const nodeKeys = document.querySelectorAll('[id^="widget.cbid.json"][id$="name"]');
+       for (let i = 0; i < nodeKeys.length; i++) {
+               let subElements = {};
+               let elements = document.querySelectorAll('[id^="widget.cbid.json.' + nodeKeys[i].id.split('.')[3] + '\."]');
+               for (const element of elements) {
+                       let key = element.id.split('.')[4];
+                       let value = element.value || "";
+                       if (value === "") {
+                               continue;
+                       }
+                       switch (key) {
+                               case 'url':
+                                       subElements.url = value;
+                                       break;
+                               case 'rule':
+                                       subElements.rule = value;
+                                       break;
+                               case 'size':
+                                       subElements.size = value;
+                                       break;
+                               case 'descr':
+                                       subElements.descr = value;
+                                       break;
+                       }
+               }
+               if (nodeKeys[i].value !== "" && subElements.descr !== "") {
+                       sumSubElements.push(nodeKeys[i].value, subElements);
+               }
+       }
+       if (sumSubElements.length > 0) {
+               exportJson = JSON.stringify(sumSubElements).replace(/^\[/, '{\n').replace(/\}]$/, '\n\t}\n}\n').replace(/,{"/g, ':{\n\t"').replace(/"},"/g, '"\n\t},\n"').replace(/","/g, '",\n\t"');
+       }
+       return fs.write('/etc/adblock/adblock.custom.feeds', exportJson).then(function () {
+               location.reload();
+       });
+}
+
+return view.extend({
+       load: function () {
+               return L.resolveDefault(fs.stat('/etc/adblock/adblock.custom.feeds'), "")
+                       .then(function (stat) {
+                       if (!stat) {
+                               return fs.write('/etc/adblock/adblock.custom.feeds', "");
+                       }
+                       return L.resolveDefault(fs.read_direct('/etc/adblock/adblock.custom.feeds', 'json'), "");
+               });
+       },
+
+       render: function (data) {
+               let m, s, o, feed, url, rule, size, descr;
+
+               m = new form.JSONMap(data, null, _('With this editor you can upload your local custom feed file or fill up an initial one (a 1:1 copy of the version shipped with the package). \
+                       The file is located at \'/etc/adblock/adblock.custom.feeds\'. \
+                       Then you can edit this file, delete entries, add new ones or make a local backup. To go back to the maintainers version just clear the custom feed file.'));
+               for (let i = 0; i < Object.keys(m.data.data).length; i++) {
+                       feed = Object.keys(m.data.data)[i];
+                       url = m.data.data[feed].url;
+                       rule = m.data.data[feed].rule;
+                       size = m.data.data[feed].size;
+                       descr = m.data.data[feed].descr;
+
+                       s = m.section(form.TypedSection, feed, null);
+                       s.addremove = true;
+                       s.anonymous = true;
+
+                       o = s.option(form.Value, 'name', _('Feed Name'));
+                       o.ucioption = '.name';
+                       o.datatype = 'and(minlength(3),maxlength(20))';
+                       o.validate = function (section_id, value) {
+                               if (!value) {
+                                       return _('Empty field not allowed');
+                               }
+                               if (!value.match(/^[a-z0-9_]+$/)) {
+                                       return _('Invalid characters');
+                               }
+                               return true;
+                       }
+
+                       o = s.option(form.Value, 'url', _('URL'));
+                       o.validate = function (section_id, value) {
+                               if (!value) {
+                                       return true;
+                               }
+                               if (!value.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-\?\&\+_@%=:~#]+$/)) {
+                                       return _('Protocol/URL format not supported');
+                               }
+                               return true;
+                       }
+
+                       o = s.option(form.ListValue, 'rule', _('Rule'));
+                       o.value('/^([[:alnum:]_-]{1,63}\\.)+[[:alpha:]]+([[:space:]]|$)/{print tolower($1)}', _('<DOMAIN>'));
+                       o.value('/^127\\.0\\.0\\.1[[:space:]]+([[:alnum:]_-]{1,63}\\.)+[[:alpha:]]+([[:space:]]|$)/{print tolower($2)}', _('127.0.0.1<SPACE><DOMAIN>'));
+                       o.value('/^0\\.0\\.0\\.0[[:space:]]+([[:alnum:]_-]{1,63}\\.)+[[:alpha:]]+([[:space:]]|$)/{print tolower($2)}', _('0.0.0.0<SPACE><DOMAIN>'));
+                       o.value('BEGIN{FS=\"[|^]\"}/^\\|\\|([[:alnum:]_-]{1,63}\\.)+[[:alpha:]]+\\^(\\$third-party)?$/{print tolower($3)}', _('<ADBLOCK-PLUS>'));
+                       o.value('BEGIN{FS=\"\/\"}/^http[s]?:\\/\\/([[:alnum:]_-]{1,63}\\.)+[[:alpha:]]+(\\/|$)/{print tolower($3)}', _('<HTTP[S]-URL>'));
+                       o.optional = true;
+                       o.rmempty = true;
+
+                       o = s.option(form.ListValue, 'size', _('Size'));
+                       o.value('S', _('Small'));
+                       o.value('M', _('Medium'));
+                       o.value('L', _('Large'));
+                       o.value('XL', _('Extra Large'));
+                       o.value('XXL', _('Extra Extra Large'));
+                       o.value('VAR', _('Varying'));
+
+                       o = s.option(form.Value, 'descr', _('Description'));
+                       o.datatype = 'and(minlength(3),maxlength(30))';
+                       o.validate = function (section_id, value) {
+                               if (!value) {
+                                       return _('Empty field not allowed');
+                               }
+                               return true;
+                       }
+               }
+
+               s = m.section(form.NamedSection, 'global');
+               s.render = L.bind(function () {
+                       return E('div', { 'class': 'cbi-page-actions' }, [
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-action important',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'id': 'btnDownload',
+                                       'disabled': 'disabled',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               return handleEdit('download');
+                                       })
+                               }, [_('Download')]),
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-action important',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'id': 'btnUpload',
+                                       'disabled': 'disabled',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               return handleEdit('upload');
+                                       })
+                               }, [_('Upload')]),
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-action important',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'id': 'btnCreate',
+                                       'disabled': 'disabled',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               return handleEdit('create');
+                                       })
+                               }, [_('Fill')]),
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-negative important',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'id': 'btnClear',
+                                       'disabled': 'disabled',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               return handleEdit('clear');
+                                       })
+                               }, [_('Clear')]),
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-positive important',
+                                       'style': 'float:none',
+                                       'id': 'btnSave',
+                                       'disabled': 'disabled',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               return handleEdit('save');
+                                       })
+                               }, [_('Save')]),
+                       ])
+               });
+               return m.render();
+       },
+       handleSaveApply: null,
+       handleSave: null,
+       handleReset: null
+});
index 9f0de8beb44e1327895b79fedd15cbec9b55f617..a17a56f92c09334bc1291d03c61251b9fe37f3e9 100644 (file)
@@ -1,4 +1,5 @@
 'use strict';
+'require dom';
 'require view';
 'require poll';
 'require fs';
        button handling
 */
 function handleAction(ev) {
-       if (ev === 'timer') {
-               L.ui.showModal(_('Refresh Timer'), [
-                       E('p', _('To keep your adblock lists up-to-date, you should set up an automatic update job for these lists.')),
-                       E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
-                               E('h5', _('Existing job(s)')),
-                               E('textarea', {
-                                       'id': 'cronView',
-                                       'style': 'width: 100% !important; padding: 5px; font-family: monospace',
-                                       'readonly': 'readonly',
-                                       'wrap': 'off',
-                                       'rows': 5
-                               })
-                       ]),
-                       E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
-                               E('label', { 'class': 'cbi-input-select', 'style': 'padding-top:.5em' }, [
-                               E('h5', _('Set a new adblock job')),
-                               E('select', { 'class': 'cbi-input-select', 'id': 'timerA' }, [
-                                       E('option', { 'value': 'start' }, 'Start'),
-                                       E('option', { 'value': 'reload' }, 'Reload'),
-                                       E('option', { 'value': 'restart' }, 'Restart'),
-                                       E('option', { 'value': 'suspend' }, 'Suspend'),
-                                       E('option', { 'value': 'resume' }, 'Resume'),
-                                       E('option', { 'value': 'report gen' }, 'Report'),
-                                       E('option', { 'value': 'report mail' }, 'Report &amp; Mail')
-                               ]),
-                               '\xa0\xa0\xa0',
-                               _('Adblock action')
-                               ]),
-                               E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
-                               E('input', { 'class': 'cbi-input-text', 'id': 'timerH', 'maxlength': '2' }, [
-                               ]),
-                               '\xa0\xa0\xa0',
-                               _('The hours portition (req., range: 0-23)')
-                               ]),
-                               E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
-                               E('input', { 'class': 'cbi-input-text', 'id': 'timerM', 'maxlength': '2' }),
-                               '\xa0\xa0\xa0',
-                               _('The minutes portion (opt., range: 0-59)')
-                               ]),
-                               E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
-                               E('input', { 'class': 'cbi-input-text', 'id': 'timerD', 'maxlength': '13' }),
-                               '\xa0\xa0\xa0',
-                               _('The day of the week (opt., values: 0-6 possibly sep. by , or -)')
-                               ])
-                       ]),
-                       E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
-                               E('label', { 'class': 'cbi-input-select', 'style': 'padding-top:.5em' }, [
-                                       E('h5', _('Remove an existing job')),
-                                       E('input', { 'class': 'cbi-input-text', 'id': 'lineno', 'maxlength': '2' }, [
-                                       ]),
-                                       '\xa0\xa0\xa0',
-                                       _('Line number to remove')
-                               ])
-                       ]),
-                       E('div', { 'class': 'right' }, [
-                               E('button', {
-                                       'class': 'btn cbi-button',
-                                       'click': L.hideModal
-                               }, _('Cancel')),
-                               ' ',
-                               E('button', {
-                                       'class': 'btn cbi-button-action',
-                                       'click': ui.createHandlerFn(this, function(ev) {
-                                               var lineno  = document.getElementById('lineno').value;
-                                               var action  = document.getElementById('timerA').value;
-                                               var hours   = document.getElementById('timerH').value;
-                                               var minutes = document.getElementById('timerM').value || '0';
-                                               var days    = document.getElementById('timerD').value || '*';
-                                               if (hours) {
-                                                       L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['timer', 'add', action, hours, minutes, days]))
-                                                       .then(function(res) {
-                                                               if (res) {
-                                                                       ui.addNotification(null, E('p', _('The Refresh Timer could not been updated.')), 'error');
-                                                               } else {
-                                                                       ui.addNotification(null, E('p', _('The Refresh Timer has been updated.')), 'info');
-                                                               }
-                                                       });
-                                               } else if (lineno) {
-                                                       L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['timer', 'remove', lineno]))
-                                                       .then(function(res) {
-                                                               if (res) {
-                                                                       ui.addNotification(null, E('p', _('The Refresh Timer could not been updated.')), 'error');
-                                                               } else {
-                                                                       ui.addNotification(null, E('p', _('The Refresh Timer has been updated.')), 'info');
-                                                               }
-                                                       });
-                                               } else {
-                                                       document.getElementById('timerH').focus();
-                                                       return
-                                               }
-                                               L.hideModal();
-                                       })
-                               }, _('Save'))
-                       ])
-               ]);
-               L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['timer', 'list']))
-               .then(function(res) {
-                       document.getElementById('cronView').value = res.trim();
-               });
-               document.getElementById('timerH').focus();
-               return
-       }
-
-       if (document.getElementById('status') && document.getElementById('status').textContent.substr(0,6) === 'paused') {
+       if (ev !== 'stop' &&
+               document.getElementById('status') &&
+               document.getElementById('status').textContent.substring(0, 6) === 'paused') {
                ev = 'resume';
        }
-
-       fs.exec_direct('/etc/init.d/adblock', [ev])
+       if (ev === 'restart' || ev === 'reload') {
+               let map = document.querySelector('.cbi-map');
+               dom.callClassMethod(map, 'save')
+                       .then(L.bind(ui.changes.apply, ui.changes))
+                       .then(function () {
+                               return fs.exec_direct('/etc/init.d/adblock', [ev]);
+                       })
+       } else {
+               return fs.exec_direct('/etc/init.d/adblock', [ev]);
+       }
 }
 
 return view.extend({
-       load: function() {
+       load: function () {
                return Promise.all([
-                       L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['list']), {}),
+                       L.resolveDefault(fs.read_direct('/etc/adblock/adblock.custom.feeds'), ''),
+                       L.resolveDefault(fs.read_direct('/etc/adblock/adblock.feeds'), ''),
                        L.resolveDefault(fs.read_direct('/etc/adblock/adblock.categories'), ''),
                        uci.load('adblock')
                ]);
        },
 
-       render: function(result) {
+       render: function (result) {
                let m, s, o;
 
                m = new form.Map('adblock', 'Adblock', _('Configuration of the adblock package to block ad/abuse domains by using DNS. \
@@ -139,10 +48,19 @@ return view.extend({
                /*
                        poll runtime information
                */
-               pollData: poll.add(function() {
-                       return L.resolveDefault(fs.read_direct('/tmp/adb_runtime.json'), 'null').then(function(res) {
-                               var info = JSON.parse(res);
+               pollData: poll.add(function () {
+                       return L.resolveDefault(fs.read_direct('/var/run/adb_runtime.json'), 'null').then(function (res) {
                                var status = document.getElementById('status');
+                               try {
+                                       var info = JSON.parse(res);
+                               } catch (e) {
+                                       status.textContent = '-';
+                                       poll.stop();
+                                       if (status.classList.contains('spinning')) {
+                                               status.classList.remove('spinning');
+                                       }
+                                       ui.addNotification(null, E('p', _('Unable to parse the runtime information!')), 'error');
+                               }
                                if (status && info) {
                                        status.textContent = (info.adblock_status || '-') + ' / ' + (info.adblock_version || '-');
                                        if (info.adblock_status === "running") {
@@ -153,39 +71,40 @@ return view.extend({
                                                if (status.classList.contains("spinning")) {
                                                        status.classList.remove("spinning");
                                                        if (document.getElementById('btn_suspend')) {
-                                                               if (status.textContent.substr(0,6) === 'paused') {
+                                                               if (status.textContent.substring(0, 6) === 'paused') {
                                                                        document.querySelector('#btn_suspend').textContent = 'Resume';
                                                                }
-                                                               if (document.getElementById('status').textContent.substr(0,7) === 'enabled') {
+                                                               if (document.getElementById('status').textContent.substring(0, 7) === 'enabled') {
                                                                        document.querySelector('#btn_suspend').textContent = 'Suspend';
                                                                }
                                                        }
                                                }
                                        }
-                                       if (status.textContent.substr(0,6) === 'paused' && document.getElementById('btn_suspend')) {
+                                       if (status.textContent.substring(0, 6) === 'paused' && document.getElementById('btn_suspend')) {
                                                document.querySelector('#btn_suspend').textContent = 'Resume';
                                        }
                                } else if (status) {
                                        status.textContent = '-';
-                                       if (status.classList.contains("spinning")) {
-                                               status.classList.remove("spinning");
+                                       poll.stop();
+                                       if (status.classList.contains('spinning')) {
+                                               status.classList.remove('spinning');
                                        }
                                }
                                var domains = document.getElementById('domains');
                                if (domains && info) {
                                        domains.textContent = info.blocked_domains || '-';
                                }
-                               var sources = document.getElementById('sources');
+                               var feeds = document.getElementById('feeds');
                                var src_array = [];
-                               if (sources && info) {
-                                       for (var i = 0; i < info.active_sources.length; i++) {
-                                               if (i < info.active_sources.length-1) {
-                                                       src_array += info.active_sources[i] + ', ';
+                               if (feeds && info) {
+                                       for (var i = 0; i < info.active_feeds.length; i++) {
+                                               if (i < info.active_feeds.length - 1) {
+                                                       src_array += info.active_feeds[i] + ', ';
                                                } else {
-                                                       src_array += info.active_sources[i]
+                                                       src_array += info.active_feeds[i]
                                                }
                                        }
-                                       sources.textContent = src_array || '-';
+                                       feeds.textContent = src_array || '-';
                                }
                                var backend = document.getElementById('backend');
                                if (backend && info) {
@@ -211,81 +130,59 @@ return view.extend({
                                if (run && info) {
                                        run.textContent = info.last_run || '-';
                                }
+                               var sys = document.getElementById('sys');
+                               if (sys && info) {
+                                       sys.textContent = info.system_info || '-';
+                               }
                        });
-               }, 1);
+               }, 2);
 
                /*
                        runtime information and buttons
                */
                s = m.section(form.NamedSection, 'global');
-               s.render = L.bind(function(view, section_id) {
+               s.render = L.bind(function (view, section_id) {
                        return E('div', { 'class': 'cbi-section' }, [
-                               E('h3', _('Information')), 
+                               E('h3', _('Information')),
                                E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Status / Version')),
-                                       E('div', { 'class': 'cbi-value-field spinning', 'id': 'status', 'style': 'color:#37c' },'\xa0')
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Status / Version')),
+                                       E('div', { 'class': 'cbi-value-field spinning', 'id': 'status', 'style': 'margin-bottom:-5px;color:#37c;' }, '\xa0')
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Blocked Domains')),
-                                       E('div', { 'class': 'cbi-value-field', 'id': 'domains', 'style': 'color:#37c' },'-')
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Blocked Domains')),
+                                       E('div', { 'class': 'cbi-value-field', 'id': 'domains', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Active Sources')),
-                                       E('div', { 'class': 'cbi-value-field', 'id': 'sources', 'style': 'color:#37c' },'-')
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Active Feeds')),
+                                       E('div', { 'class': 'cbi-value-field', 'id': 'feeds', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('DNS Backend')),
-                                       E('div', { 'class': 'cbi-value-field', 'id': 'backend', 'style': 'color:#37c' },'-')
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('DNS Backend')),
+                                       E('div', { 'class': 'cbi-value-field', 'id': 'backend', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Utils')),
-                                       E('div', { 'class': 'cbi-value-field', 'id': 'utils', 'style': 'color:#37c' },'-')
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Run Utils')),
+                                       E('div', { 'class': 'cbi-value-field', 'id': 'utils', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Interfaces')),
-                                       E('div', { 'class': 'cbi-value-field', 'id': 'ifaces', 'style': 'color:#37c' },'-')
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Run Interfaces')),
+                                       E('div', { 'class': 'cbi-value-field', 'id': 'ifaces', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Directories')),
-                                       E('div', { 'class': 'cbi-value-field', 'id': 'dirs', 'style': 'color:#37c' },'-')
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Run Directories')),
+                                       E('div', { 'class': 'cbi-value-field', 'id': 'dirs', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Flags')),
-                                       E('div', { 'class': 'cbi-value-field', 'id': 'flags', 'style': 'color:#37c' },'-')
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Run Flags')),
+                                       E('div', { 'class': 'cbi-value-field', 'id': 'flags', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ]),
                                E('div', { 'class': 'cbi-value' }, [
-                                       E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Last Run')),
-                                       E('div', { 'class': 'cbi-value-field', 'id': 'run', 'style': 'color:#37c' },'-')
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Last Run')),
+                                       E('div', { 'class': 'cbi-value-field', 'id': 'run', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ]),
-                               E('div', { class: 'right' }, [
-                                       E('button', {
-                                               'class': 'btn cbi-button cbi-button-apply',
-                                               'click': ui.createHandlerFn(this, function() {
-                                                       return handleAction('timer');
-                                               })
-                                       }, [ _('Refresh Timer...') ]),
-                                       '\xa0\xa0\xa0',
-                                       E('button', {
-                                               'class': 'btn cbi-button cbi-button-apply',
-                                               'id': 'btn_suspend',
-                                               'click': ui.createHandlerFn(this, function() {
-                                                       return handleAction('suspend');
-                                               })
-                                       }, [ _('Suspend') ]),
-                                       '\xa0\xa0\xa0',
-                                       E('button', {
-                                               'class': 'btn cbi-button cbi-button-positive',
-                                               'click': ui.createHandlerFn(this, function() {
-                                                       return handleAction('reload');
-                                               })
-                                       }, [ _('Reload') ]),
-                                       '\xa0\xa0\xa0',
-                                       E('button', {
-                                               'class': 'btn cbi-button cbi-button-negative',
-                                               'click': ui.createHandlerFn(this, function() {
-                                                       return handleAction('restart');
-                                               })
-                                       }, [ _('Restart') ])
+                               E('div', { 'class': 'cbi-value' }, [
+                                       E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('System Info')),
+                                       E('div', { 'class': 'cbi-value-field', 'id': 'sys', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
                                ])
                        ]);
                }, o, this);
@@ -296,16 +193,21 @@ return view.extend({
                */
                s = m.section(form.NamedSection, 'global', 'adblock', _('Settings'));
                s.addremove = false;
-               s.tab('general',  _('General Settings'));
+               s.tab('general', _('General Settings'));
                s.tab('additional', _('Additional Settings'));
                s.tab('adv_dns', _('Advanced DNS Settings'));
                s.tab('adv_report', _('Advanced Report Settings'));
                s.tab('adv_email', _('Advanced E-Mail Settings'));
-               s.tab('sources', _('Blocklist Sources'));
+               s.tab('feeds', _('Feed Selection'));
 
                /*
                        general settings tab
                */
+               o = s.taboption('general', form.DummyValue, '_sub');
+               o.rawhtml = true;
+               o.default = '<em style="color:#37c;font-weight:bold;">' + _('Changes on this tab needs an adblock service restart to take effect.') + '</em>'
+                       + '<hr style="width: 200px; height: 1px;" />';
+
                o = s.taboption('general', form.Flag, 'adb_enabled', _('Enabled'), _('Enable the adblock service.'));
                o.rmempty = false;
 
@@ -314,17 +216,17 @@ return view.extend({
                o.nocreate = true;
                o.rmempty = true;
 
-               o = s.taboption('general', form.Flag, 'adb_forcedns', _('Force Local DNS'), _('Redirect all DNS queries from specified zones to the local DNS resolver, applies to UDP and TCP protocol.'));
+               o = s.taboption('general', form.Flag, 'adb_dnsforce', _('Force Local DNS'), _('Redirect all DNS queries from specified zones to the local DNS resolver, applies to UDP and TCP protocol.'));
                o.rmempty = false;
 
                o = s.taboption('general', widgets.ZoneSelect, 'adb_zonelist', _('Forced Zones'), _('Firewall source zones that should be forced locally.'));
-               o.depends('adb_forcedns', '1');
+               o.depends('adb_dnsforce', '1');
                o.multiple = true;
                o.nocreate = true;
                o.rmempty = true;
 
                o = s.taboption('general', form.DynamicList, 'adb_portlist', _('Forced Ports'), _('Firewall ports that should be forced locally.'));
-               o.depends('adb_forcedns', '1');
+               o.depends('adb_dnsforce', '1');
                o.multiple = true;
                o.nocreate = false;
                o.datatype = 'port';
@@ -337,29 +239,26 @@ return view.extend({
                o.default = 1
                o.rmempty = true;
 
-               o = s.taboption('general', form.Flag, 'adb_safesearch', _('Enable SafeSearch'), _('Enforcing SafeSearch for google, bing, duckduckgo, yandex, youtube and pixabay.'));
+               o = s.taboption('general', form.Flag, 'adb_safesearch', _('Enable SafeSearch'), _('Enforcing SafeSearch for google, bing, brave, duckduckgo, yandex, youtube and pixabay.'));
                o.rmempty = false;
 
                o = s.taboption('general', form.MultiValue, 'adb_safesearchlist', _('Limit SafeSearch'), _('Limit SafeSearch to certain providers.'));
                o.depends('adb_safesearch', '1');
                o.value('google');
                o.value('bing');
+               o.value('brave');
                o.value('duckduckgo');
                o.value('yandex');
                o.value('youtube');
                o.value('pixabay');
                o.rmempty = true;
 
-               o = s.taboption('general', form.Flag, 'adb_safesearchmod', _('Relax SafeSearch'), _('Enable moderate SafeSearch filters for youtube.'));
-               o.depends('adb_safesearch', '1');
-               o.rmempty = true;
-
                o = s.taboption('general', form.Flag, 'adb_report', _('DNS Report'), _('Gather DNS related network traffic via tcpdump and provide a DNS Report on demand. \
-                       Please note: this needs additional \'tcpdump\' or \'tcpdump-mini\' package installation and a full adblock service restart to take effect.'));
+                       This needs the additional \'tcpdump\' or \'tcpdump-mini\' package installation and a full adblock service restart to take effect.'));
                o.rmempty = false;
 
                o = s.taboption('general', form.Flag, 'adb_mail', _('E-Mail Notification'), _('Send adblock related notification e-mails. \
-                       Please note: this needs additional \'msmtp\' package installation.'));
+                       This needs the additional \'msmtp\' package installation.'));
                o.rmempty = false;
 
                o = s.taboption('general', form.Value, 'adb_mailreceiver', _('E-Mail Receiver Address'), _('Receiver address for adblock notification e-mails.'));
@@ -370,11 +269,15 @@ return view.extend({
                /*
                        additional settings tab
                */
+               o = s.taboption('additional', form.DummyValue, '_sub');
+               o.rawhtml = true;
+               o.default = '<em style="color:#37c;font-weight:bold;">' + _('Changes on this tab needs an adblock service restart to take effect.') + '</em>'
+                       + '<hr style="width: 200px; height: 1px;" />';
+
                o = s.taboption('additional', form.Flag, 'adb_debug', _('Verbose Debug Logging'), _('Enable verbose debug logging in case of any processing errors.'));
                o.rmempty = false;
 
-               o = s.taboption('additional', form.Flag, 'adb_nice', _('Low Priority Service'), _('Reduce the priority of the adblock background processing to take fewer resources from the system. \
-                       Please note: This change requires a full adblock service restart to take effect.'));
+               o = s.taboption('additional', form.Flag, 'adb_nice', _('Low Priority Service'), _('Reduce the priority of the adblock background processing to take fewer resources from the system.'));
                o.enabled = '10';
                o.rmempty = true;
 
@@ -383,25 +286,19 @@ return view.extend({
                o.datatype = 'range(1,300)';
                o.rmempty = true;
 
-               o = s.taboption('additional', form.Value, 'adb_tmpbase', _('Base Temp Directory'), _('Base Temp Directory for all adblock related runtime operations, \
+               o = s.taboption('additional', form.Value, 'adb_tmpbase', _('Base Temp Directory'), _('Base temp directory for all adblock related runtime operations, \
                        e.g. downloading, sorting, merging etc.'));
                o.placeholder = '/tmp';
                o.rmempty = true;
 
-               o = s.taboption('additional', form.Flag, 'adb_backup', _('Blocklist Backup'), _('Create compressed blocklist backups, they will be used in case of download errors or during startup.'));
-               o.default = 1
-               o.rmempty = false;
-
                o = s.taboption('additional', form.Value, 'adb_backupdir', _('Backup Directory'), _('Target directory for blocklist backups.'));
-               o.depends('adb_backup', '1');
-               o.placeholder = '/tmp/adblock-Backup';
+               o.placeholder = '/tmp/adblock-backup';
                o.rmempty = true;
 
-               o = s.taboption('additional', form.ListValue, 'adb_fetchutil', _('Download Utility'), _('List of supported and fully pre-configured download utilities.'));
+               o = s.taboption('additional', form.ListValue, 'adb_fetchcmd', _('Download Utility'), _('List of supported and fully pre-configured download utilities.'));
                o.value('uclient-fetch');
                o.value('wget');
                o.value('curl');
-               o.value('aria2c');
                o.optional = true;
                o.rmempty = true;
 
@@ -409,71 +306,68 @@ return view.extend({
                o.default = 0
                o.rmempty = true;
 
-               o = s.taboption('additional', form.Value, 'adb_fetchparm', _('Download Parameters'), _('Manually override the pre-configured download options for the selected download utility.'));
-               o.optional = true;
-               o.rmempty = true;
-
                /*
                        advanced dns settings tab
                */
-               o = s.taboption('adv_dns', form.ListValue, 'adb_dns', _('DNS Backend'), _('List of supported DNS backends with their default list directory. \
-                       To overwrite the default path use the \'DNS Directory\' option.'));
-               o.value('dnsmasq', _('dnsmasq (/tmp/dnsmasq.d)'));
-               o.value('unbound', _('unbound (/var/lib/unbound)'));
-               o.value('named', _('bind (/var/lib/bind)'));
-               o.value('smartdns', _('smartdns (/tmp/smartdns)'));
-               o.value('kresd', _('kresd (/etc/kresd)'));
-               o.value('raw', _('raw (/tmp)'));
+               o = s.taboption('adv_dns', form.DummyValue, '_sub');
+               o.rawhtml = true;
+               o.default = '<em style="color:#37c;font-weight:bold;">' + _('Changes on this tab needs an adblock service restart to take effect.') + '</em>'
+                       + '<hr style="width: 200px; height: 1px;" />';
+
+               o = s.taboption('adv_dns', form.ListValue, 'adb_dns', _('DNS Backend'), _('List of supported DNS backends.'));
+               o.value('dnsmasq', _('dnsmasq'));
+               o.value('unbound', _('unbound'));
+               o.value('named', _('bind'));
+               o.value('smartdns', _('smartdns'));
+               o.value('kresd', _('kresd'));
+               o.value('raw', _('raw'));
                o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('adv_dns', form.Value, 'adb_dnsdir', _('DNS Directory'), _('Target directory for the generated blocklist \'adb_list.overall\'.'));
-               o.placeholder = '/tmp';
+               o = s.taboption('adv_dns', form.Flag, 'adb_dnsshift', _('Shift DNS Blocklist'), _('Shift the final DNS blocklist to the backup directory and only set a soft link to this file in memory. \
+                       As long as your backup directory resides on an external drive, enable this option to save memory.'));
+               o.rmempty = true;
+
+               o = s.taboption('adv_dns', form.Flag, 'adb_dnsflush', _('Flush DNS Cache'), _('Empty the DNS cache before adblock processing starts to reduce the memory consumption.'));
+               o.rmempty = true;
+
+               o = s.taboption('adv_dns', form.Value, 'adb_lookupdomain', _('DNS Lookup Domain'), _('Domain to check for a successful DNS backend restart.'));
+               o.placeholder = 'localhost';
+               o.rmempty = true;
+
+               o = s.taboption('adv_dns', form.Value, 'adb_dnsdir', _('DNS Directory'), _('Overwrite the default target directory for the generated blocklist.'));
                o.rmempty = true;
 
                o = s.taboption('adv_dns', form.ListValue, 'adb_dnsinstance', _('DNS Instance'), _('Set the dns backend instance used by adblock.'));
+               o.depends('adb_dns', 'dnsmasq');
                o.value('0', _('First instance (default)'));
                o.value('1', _('Second instance'));
                o.value('2', _('Third instance'));
-               o.value('3', _('Fourth instance'));
-               o.value('4', _('Fifth instance'));
-               o.depends('adb_dns', 'dnsmasq');
                o.optional = true;
                o.rmempty = true;
 
                o = s.taboption('adv_dns', form.Value, 'adb_dnstimeout', _('DNS Restart Timeout'), _('Timeout to wait for a successful DNS backend restart.'));
                o.placeholder = '20';
-               o.datatype = 'range(1,60)';
-               o.rmempty = true;
-
-               o = s.taboption('adv_dns', form.Value, 'adb_lookupdomain', _('External DNS Lookup Domain'), _('External domain to check for a successful DNS backend restart. \
-                       Please note: To disable this check set this option to \'false\'.'));
-               o.placeholder = 'example.com';
+               o.datatype = 'range(5,60)';
                o.rmempty = true;
 
-               o = s.taboption('adv_dns', form.Flag, 'adb_dnsflush', _('Flush DNS Cache'), _('Empty the DNS cache before adblock processing starts to reduce the memory consumption.'));
-               o.rmempty = true;
-
-               o = s.taboption('adv_dns', form.Flag, 'adb_dnsallow', _('Disable DNS Allow'), _('Disable selective DNS whitelisting (RPZ-PASSTHRU).'));
-               o.rmempty = true;
-
-               o = s.taboption('adv_dns', form.DynamicList, 'adb_denyip', _('Block Local Client IPs'), _('Block all requests of certain DNS clients based on their IP address (RPZ-CLIENT-IP). \
-                       Please note: This feature is currently only supported by bind DNS backend.'));
+               o = s.taboption('adv_dns', form.DynamicList, 'adb_denyip', _('Block Local Client IPs'), _('Block all requests of certain DNS clients based on their IP address (RPZ-CLIENT-IP).'));
                o.datatype = 'or(ip4addr("nomask"),ip6addr("nomask"))';
+               o.depends('adb_dns', 'bind');
                o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('adv_dns', form.DynamicList, 'adb_allowip', _('Allow Local Client IPs'), _('Allow all requests of certain DNS clients based on their IP address (RPZ-CLIENT-IP). \
-                       Please note: This feature is currently only supported by bind DNS backend.'));
+               o = s.taboption('adv_dns', form.DynamicList, 'adb_allowip', _('Allow Local Client IPs'), _('Allow all requests of certain DNS clients based on their IP address (RPZ-CLIENT-IP).'));
                o.datatype = 'or(ip4addr("nomask"),ip6addr("nomask"))';
+               o.depends('adb_dns', 'bind');
                o.optional = true;
                o.rmempty = true;
 
                o = s.taboption('adv_dns', form.Flag, 'adb_jail', _('Additional Jail Blocklist'), _('Builds an additional DNS blocklist to block access to all domains except those listed in the whitelist. \
-                       Please note: You can use this restrictive blocklist e.g. for guest wifi or kidsafe configurations.'));
+                       You can use this restrictive blocklist e.g. for guest wifi or kidsafe configurations.'));
                o.rmempty = true;
 
-               o = s.taboption('adv_dns', form.Value, 'adb_jaildir', _('Jail Directory'), _('Target directory for the generated jail blocklist \'adb_list.jail\'.'));
+               o = s.taboption('adv_dns', form.Value, 'adb_jaildir', _('Jail Directory'), _('Target directory for the generated jail blocklist.'));
                o.depends('adb_jail', '1');
                o.placeholder = '/tmp';
                o.rmempty = true;
@@ -481,6 +375,11 @@ return view.extend({
                /*
                        advanced report settings tab
                */
+               o = s.taboption('adv_report', form.DummyValue, '_sub');
+               o.rawhtml = true;
+               o.default = '<em style="color:#37c;font-weight:bold;">' + _('Changes on this tab needs an adblock service restart to take effect.') + '</em>'
+                       + '<hr style="width: 200px; height: 1px;" />';
+
                o = s.taboption('adv_report', form.DummyValue, '_sub');
                o.rawhtml = true;
                o.default = '<em><b>Changes on this tab needs a full adblock service restart to take effect.</b></em>';
@@ -490,7 +389,7 @@ return view.extend({
                o.rmempty = true;
 
                o = s.taboption('adv_report', form.Value, 'adb_reportdir', _('Report Directory'), _('Target directory for DNS related report files.'));
-               o.placeholder = '/tmp/adblock-Report';
+               o.placeholder = '/tmp/adblock-report';
                o.rmempty = true;
 
                o = s.taboption('adv_report', form.Value, 'adb_repchunkcnt', _('Report Chunk Count'), _('Report chunk count used by tcpdump.'));
@@ -513,6 +412,11 @@ return view.extend({
                /*
                        advanced email settings tab
                */
+               o = s.taboption('adv_email', form.DummyValue, '_sub');
+               o.rawhtml = true;
+               o.default = '<em style="color:#37c;font-weight:bold;">' + _('Changes on this tab needs an adblock service restart to take effect.') + '</em>'
+                       + '<hr style="width: 200px; height: 1px;" />';
+
                o = s.taboption('adv_email', form.Value, 'adb_mailsender', _('E-Mail Sender Address'), _('Sender address for adblock notification E-Mails.'));
                o.placeholder = 'no-reply@adblock';
                o.rmempty = true;
@@ -526,47 +430,56 @@ return view.extend({
                o.rmempty = true;
 
                /*
-                       blocklist sources tab
+                       feed selection tab
                */
-               o = s.taboption('sources', form.DummyValue, '_sub');
-               o.rawhtml = true;
-               o.default = '<em><b>List of supported and fully pre-configured adblock sources.</b></em><br /> \
-                       List size information with the respective domain ranges as follows:<br /> \
-                       &#8226;&#xa0;<b>S</b> (-10k), <b>M</b> (10k-30k) and <b>L</b> (30k-80k) should work for 128 MByte devices,<br /> \
-                       &#8226;&#xa0;<b>XL</b> (80k-200k) should work for 256-512 MByte devices,<br /> \
-                       &#8226;&#xa0;<b>XXL</b> (200k-) needs more RAM and Multicore support, e.g. x86 or raspberry devices.<br /> \
-                       &#8226;&#xa0;<b>VAR</b> (50k-500k) variable size depending on the selection.<br />';
-
-               var name, size, focus, sources = [];
-               if (result[0]) {
-                       sources = result[0].trim().split('\n');
+               let feed, feeds, chain, descr;
+               if (result && Object.keys(result).length) {
+                       if (result[0]) {
+                               try {
+                                       feeds = JSON.parse(result[0]);
+                               } catch (e) {
+                                       ui.addNotification(null, E('p', _('Unable to parse the custom feed file!')), 'error');
+                               }
+                       }
+                       if (result[1] && (!feeds || (feeds && !Object.keys(feeds).length))) {
+                               try {
+                                       feeds = JSON.parse(result[1]);
+                               } catch (e) {
+                                       ui.addNotification(null, E('p', _('Unable to parse the default feed file!')), 'error');
+                               }
+                       }
                }
-
-               o = s.taboption('sources', form.MultiValue, 'adb_sources', _('Sources (Size, Focus)'));
-               for (var i = 0; i < sources.length; i++) {
-                       if (sources[i].match(/^\s+\+/)) {
-                               name  = sources[i].match(/^\s+\+\s(\w+)\s/)[1] || '-';
-                               size  = sources[i].match(/^\s+\+\s\w+[\sx]+(\w+)/)[1] || '-';
-                               focus = sources[i].match(/^\s+\+\s\w+[\sx]+\w+\s+([\w\+]+)/)[1] || '-';
-                               o.value(name, name + ' (' + size + ', ' + focus + ')');
+               o = s.taboption('feeds', form.DummyValue, '_sub');
+               o.rawhtml = true;
+               o.default = '<em style="color:#37c;font-weight:bold;">' + _('Changes on this tab needs an adblock service reload to take effect.') + '</em>'
+                       + '<hr style="width: 200px; height: 1px;" />'
+                       + '<em style="color:#37c;font-weight:bold;">' + _('External Blocklist Feeds') + '</em>';
+
+               if (feeds && Object.keys(feeds).length) {
+                       o = s.taboption('feeds', form.MultiValue, 'adb_feed', _('Blocklist Feed'));
+                       for (let i = 0; i < Object.keys(feeds).length; i++) {
+                               feed = Object.keys(feeds)[i].trim();
+                               chain = feeds[feed].size.trim() || 'in';
+                               descr = feeds[feed].descr.trim() || '-';
+                               o.value(feed, feed + ' (' + chain + ', ' + descr + ')');
                        }
+                       o.optional = true;
+                       o.rmempty = true;
                }
-               o.optional = true;
-               o.rmempty = true;
 
                /*
                        prepare category data
                */
                var code, category, list, path, categories = [];
-               if (result[1]) {
-                       categories = result[1].trim().split('\n');
+               if (result[2]) {
+                       categories = result[2].trim().split('\n');
                }
 
-               o = s.taboption('sources', form.DummyValue, '_sub');
+               o = s.taboption('feeds', form.DummyValue, '_sub');
                o.rawhtml = true;
-               o.default = '<em><b>UTCapitole Archive Selection</b></em>';
+               o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('UTCapitole Archive Selection') + '</em>';
 
-               o = s.taboption('sources', form.DynamicList, 'adb_utc_sources', _('Categories'));
+               o = s.taboption('feeds', form.DynamicList, 'adb_utc_feed', _('Categories'));
                for (var i = 0; i < categories.length; i++) {
                        code = categories[i].match(/^(\w+);/)[1].trim();
                        if (code === 'utc') {
@@ -577,11 +490,11 @@ return view.extend({
                o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('sources', form.DummyValue, '_sub');
+               o = s.taboption('feeds', form.DummyValue, '_sub');
                o.rawhtml = true;
-               o.default = '<em><b>StevenBlack List Selection</b></em>';
+               o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('StevenBlack List Selection') + '</em>';
 
-               o = s.taboption('sources', form.DynamicList, 'adb_stb_sources', _('Variants'));
+               o = s.taboption('feeds', form.DynamicList, 'adb_stb_feed', _('Categories'));
                for (var i = 0; i < categories.length; i++) {
                        code = categories[i].match(/^(\w+);/)[1].trim();
                        if (code === 'stb') {
@@ -593,11 +506,11 @@ return view.extend({
                o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('sources', form.DummyValue, '_sub');
+               o = s.taboption('feeds', form.DummyValue, '_sub');
                o.rawhtml = true;
-               o.default = '<em><b>Hagezi List Selection</b></em>';
+               o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('Hagezi List Selection') + '</em>';
 
-               o = s.taboption('sources', form.DynamicList, 'adb_hag_sources', _('Variants'));
+               o = s.taboption('feeds', form.DynamicList, 'adb_hag_feed', _('Categories'));
                for (var i = 0; i < categories.length; i++) {
                        code = categories[i].match(/^(\w+);/)[1].trim();
                        if (code === 'hag') {
@@ -609,11 +522,11 @@ return view.extend({
                o.optional = true;
                o.rmempty = true;
 
-               o = s.taboption('sources', form.DummyValue, '_sub');
+               o = s.taboption('feeds', form.DummyValue, '_sub');
                o.rawhtml = true;
-               o.default = '<em><b>1Hosts List Selection</b></em>';
+               o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('1Hosts List Selection') + '</em>';
 
-               o = s.taboption('sources', form.DynamicList, 'adb_hst_sources', _('Variants'));
+               o = s.taboption('feeds', form.DynamicList, 'adb_hst_feed', _('Categories'));
                for (var i = 0; i < categories.length; i++) {
                        code = categories[i].match(/^(\w+);/)[1].trim();
                        if (code === 'hst') {
@@ -625,7 +538,43 @@ return view.extend({
                o.optional = true;
                o.rmempty = true;
 
+               s = m.section(form.NamedSection, 'global');
+               s.render = L.bind(function () {
+                       return E('div', { 'class': 'cbi-page-actions' }, [
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-negative important',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               return handleAction('stop');
+                                       })
+                               }, [_('Stop')]),
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-apply important',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'id': 'btn_suspend',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               return handleAction('suspend');
+                                       })
+                               }, [_('Suspend')]),
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-positive important',
+                                       'style': 'float:none;margin-right:.4em;',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               return handleAction('reload');
+                                       })
+                               }, [_('Save & Reload')]),
+                               E('button', {
+                                       'class': 'btn cbi-button cbi-button-positive important',
+                                       'style': 'float:none',
+                                       'click': ui.createHandlerFn(this, function () {
+                                               handleAction('restart');
+                                       })
+                               }, [_('Save & Restart')])
+                       ])
+               });
                return m.render();
        },
+       handleSaveApply: null,
+       handleSave: null,
        handleReset: null
 });
diff --git a/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/whitelist.js b/applications/luci-app-adblock/htdocs/luci-static/resources/view/adblock/whitelist.js
deleted file mode 100644 (file)
index 9ffdbbd..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-'use strict';
-'require view';
-'require fs';
-'require ui';
-
-return view.extend({
-       load: function() {
-               return L.resolveDefault(fs.read_direct('/etc/adblock/adblock.whitelist'), '');
-       },
-       handleSave: function(ev) {
-               var value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n/g, '\n')) + '\n';
-               return fs.write('/etc/adblock/adblock.whitelist', value)
-                       .then(function(rc) {
-                               document.querySelector('textarea').value = value;
-                               ui.addNotification(null, E('p', _('The changes to the whitelist have been saved. Reload your adblock lists for the changes to take effect.')), 'info');
-                       }).catch(function(e) {
-                               ui.addNotification(null, E('p', _('Unable to save changes: %s').format(e.message)));
-                       });
-       },
-       render: function(whitelist) {
-               return E([
-                       E('p', {},
-                               _('This is the local adblock whitelist to always allow certain (sub) domains.<br /> \
-                               Please note: add only one domain per line. Comments introduced with \'#\' are allowed - ip addresses, wildcards and regex are not.')),
-                       E('p', {},
-                               E('textarea', {
-                                       'style': 'width: 100% !important; padding: 5px; font-family: monospace',
-                                       'spellcheck': 'false',
-                                       'wrap': 'off',
-                                       'rows': 25
-                               }, [ whitelist != null ? whitelist : '' ])
-                       )
-               ]);
-       },
-       handleSaveApply: null,
-       handleReset: null
-});
index 9f2659c1da86181e0b24332504e7b998cce6f6cf..807310b957886eb0d7693032861210b2e8475c6b 100644 (file)
                        "path": "adblock/overview"
                }
        },
-       "admin/services/adblock/dnsreport": {
-               "title": "DNS Report",
+       "admin/services/adblock/allowlist": {
+               "title": "Edit Allowlist",
                "order": 20,
                "action": {
                        "type": "view",
-                       "path": "adblock/dnsreport"
+                       "path": "adblock/allowlist"
                }
        },
-       "admin/services/adblock/blacklist": {
-               "title": "Edit Blacklist",
+       "admin/services/adblock/blocklist": {
+               "title": "Edit Blocklist",
                "order": 30,
                "action": {
                        "type": "view",
-                       "path": "adblock/blacklist"
+                       "path": "adblock/blocklist"
                }
        },
-       "admin/services/adblock/whitelist": {
-               "title": "Edit Whitelist",
+       "admin/services/adblock/feeds": {
+               "title": "Custom Feed Editor",
                "order": 40,
                "action": {
                        "type": "view",
-                       "path": "adblock/whitelist"
+                       "path": "adblock/feeds"
+               }
+       },
+       "admin/services/adblock/dnsreport": {
+               "title": "DNS Report",
+               "order": 50,
+               "action": {
+                       "type": "view",
+                       "path": "adblock/dnsreport"
                }
        },
        "admin/services/adblock/logread": {
index 61b6f3b57d6909fe3fe3ce08586946c68874bc60..5fc914d3e76756d77a3d45eeda68011a17113199 100644 (file)
@@ -2,33 +2,68 @@
        "luci-app-adblock": {
                "description": "Grant access to LuCI app adblock",
                "write": {
-                       "uci": ["adblock"],
+                       "uci": [
+                               "adblock"
+                       ],
                        "file": {
-                               "/etc/adblock/*": ["read"],
-                               "/etc/adblock/adblock.blacklist": ["write"],
-                               "/etc/adblock/adblock.whitelist": ["write"]
+                               "/etc/adblock/*": [
+                                       "read",
+                                       "write"
+                               ],
+                               "/etc/adblock/adblock.allowlist": [
+                                       "write"
+                               ],
+                               "/etc/adblock/adblock.blocklist": [
+                                       "write"
+                               ],
+                               "/etc/adblock/adblock.custom.feeds": [
+                                       "read",
+                                       "write"
+                               ]
                        }
                },
                "read": {
-                       "cgi-io": [ "exec" ],
+                       "cgi-io": [
+                               "exec"
+                       ],
                        "file": {
-                               "/var/run/adblock.pid": ["read"],
-                               "/tmp/adb_runtime.json": ["read"],
-                               "/etc/crontabs/root": ["read"],
-                               "/sbin/logread -e adblock-": [ "exec" ],
-                               "/usr/sbin/logread -e adblock-": [ "exec" ],
-                               "/etc/init.d/adblock list" : [ "exec" ],
-                               "/etc/init.d/adblock reload" : [ "exec" ],
-                               "/etc/init.d/adblock restart" : [ "exec" ],
-                               "/etc/init.d/adblock suspend" : [ "exec" ],
-                               "/etc/init.d/adblock resume" : [ "exec" ],
-                               "/etc/init.d/adblock report [a-z]* [0-9]* [0-9]* *" : [ "exec" ],
-                               "/etc/init.d/adblock timer list" : [ "exec" ],
-                               "/etc/init.d/adblock timer remove [0-9]*" : [ "exec" ],
-                               "/etc/init.d/adblock timer add * [0-9]* [0-9*]* [0-6,-*]*" : [ "exec" ],
-                               "/etc/init.d/adblock query *" : [ "exec" ]
+                               "/var/run/adblock.pid": [
+                                       "read"
+                               ],
+                               "/var/run/adb_runtime.json": [
+                                       "read"
+                               ],
+                               "/sbin/logread -e adblock-": [
+                                       "exec"
+                               ],
+                               "/usr/sbin/logread -e adblock-": [
+                                       "exec"
+                               ],
+                               "/etc/init.d/adblock reload": [
+                                       "exec"
+                               ],
+                               "/etc/init.d/adblock restart": [
+                                       "exec"
+                               ],
+                               "/etc/init.d/adblock suspend": [
+                                       "exec"
+                               ],
+                               "/etc/init.d/adblock resume": [
+                                       "exec"
+                               ],
+                               "/etc/init.d/adblock stop": [
+                                       "exec"
+                               ],
+                               "/etc/init.d/adblock report [a-z]* [0-9]* [0-9]* *": [
+                                       "exec"
+                               ],
+                               "/etc/init.d/adblock query *": [
+                                       "exec"
+                               ]
                        },
-                       "uci": ["adblock"]
+                       "uci": [
+                               "adblock"
+                       ]
                }
        }
-}
+}
\ No newline at end of file